using System.ComponentModel;
using FireStarterModeller.BO;
using Habanero.Base;

namespace FireStarterModeller.UI.DetailsView
{
    public class SolutionViewModel
    {
        private AssemblyModel _currentAssembly;


        public delegate void ClassEvent(ClassModel classModel);

        public delegate void AssemblyEvent(AssemblyModel assemblyModel);

        public event AssemblyEvent CurrentAssemblyChanged;
        public event AssemblyEvent AssemblyAdded;
        public event AssemblyEvent AssemblyRemoved;

        public SolutionViewModel(string name)
        {
            Name = name;
            Assemblies = new ListWithEvents<AssemblyModel>();
            Assemblies.ItemAdded += FireAssemblyAddedEvent;
            Assemblies.ItemRemoved += item =>
                                      {
                                          FireAssemblyRemovedEvent(item);
                                          item.DisableEvents();
                                      };
        }

        public DMSolution DMSolution { get; internal set; }
        public ListWithEvents<AssemblyModel> Assemblies { get; private set; }
        public string Name { get; private set; }

        public AssemblyModel CurrentAssembly
        {
            get { return _currentAssembly ?? Assemblies[0]; }
            set
            {
                _currentAssembly = value;
                FireCurrentAssemblyChangedEvent();
            }
        }

        internal void AddAssemblyToModel(DMAssembly assembly)
        {
            AssemblyModel assemblyModel = new AssemblyModel(assembly.Name) {DMAssembly = assembly};
            this.Assemblies.Add(assemblyModel);
            assembly.Classes.BusinessObjectAdded += ClassAdded_Handler;
            assembly.Classes.BusinessObjectRemoved += ClassRemoved_Handler;
            foreach (DMClass dmClass in assembly.Classes)
            {
                assemblyModel.AddDMClass(dmClass);
            }

            assembly.PropertyUpdated += Assembly_OnPropertyUpdated;
        }

        internal void RemoveAssemblyFromModel(DMAssembly assembly)
        {
            AssemblyModel assemblyModel = GetModelForAssembly(assembly);
            if (assemblyModel == null) return;
            this.Assemblies.Remove(assemblyModel);

            assembly.PropertyUpdated -= Assembly_OnPropertyUpdated;
            assembly.Classes.BusinessObjectAdded -= ClassAdded_Handler;
            assembly.Classes.BusinessObjectRemoved -= ClassRemoved_Handler;
            assemblyModel.Classes.Clear();
        }

        private void ClassRemoved_Handler(object sender, BOEventArgs<DMClass> e)
        {
            DMClass dmClass = e.BusinessObject;
            GetModelForAssembly(dmClass.Assembly).RemoveDMClass(dmClass);
        }

        private void ClassAdded_Handler(object sender, BOEventArgs<DMClass> e)
        {
            DMClass dmClass = e.BusinessObject;
            GetModelForAssembly(dmClass.Assembly).AddDMClass(dmClass);
        }

        private void Assembly_OnPropertyUpdated(object sender, BOPropUpdatedEventArgs e)
        {
            DMAssembly assembly = (DMAssembly) e.BusinessObject;
            GetModelForAssembly(assembly).Name = assembly.Name;
        }

        private AssemblyModel GetModelForAssembly(DMAssembly dmAssembly)
        {
            foreach (AssemblyModel assemblyModel in Assemblies)
            {
                if (assemblyModel.DMAssembly == dmAssembly) return assemblyModel;
            }
            return null;
        }

        private void FireAssemblyRemovedEvent(AssemblyModel assemblyModel)
        {
            if (this.AssemblyRemoved != null) this.AssemblyRemoved(assemblyModel);
        }

        private void FireAssemblyAddedEvent(AssemblyModel assemblyModel)
        {
            if (this.AssemblyAdded != null) this.AssemblyAdded(assemblyModel);
        }

        private void FireCurrentAssemblyChangedEvent()
        {
            if (CurrentAssemblyChanged != null) this.CurrentAssemblyChanged(_currentAssembly);
        }


        public class AssemblyModel : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            public event ClassEvent ClassAdded;
            public event ClassEvent ClassRemoved;
            public event ClassEvent CurrentClassChanged;

            private string _name;
            private readonly ListWithEvents<ClassModel> _classes;
            private ClassModel _currentClass;

            public AssemblyModel(string name)
            {
                _name = name;
                _classes = new ListWithEvents<ClassModel>();
                _classes.ItemAdded += FireClassAddedEvent;
                _classes.ItemRemoved += FireClassRemovedEvent;
            }


            private void FireClassRemovedEvent(ClassModel classModel)
            {
                if (this.ClassRemoved != null) this.ClassRemoved(classModel);
            }


            private void FireClassAddedEvent(ClassModel classModel)
            {
                if (this.ClassAdded != null) this.ClassAdded(classModel);
            }

            public ListWithEvents<ClassModel> Classes
            {
                get { return _classes; }
            }

            public string Name
            {
                get { return _name; }
                set
                {
                    _name = value;
                    FirePropertyChangedEvent("Name");
                }
            }

            private void FirePropertyChangedEvent(string propertyName)
            {
                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }

            public ClassModel CurrentClass
            {
                get { return _currentClass; }
                set
                {
                    _currentClass = value;
                    FireCurrentClassChangedEvent();
                }
            }

            public DMAssembly DMAssembly { get; internal set; }

            private void FireCurrentClassChangedEvent()
            {
                if (this.CurrentClassChanged != null) this.CurrentClassChanged(_currentClass);
            }

            internal void DisableEvents()
            {
                _classes.ItemAdded -= FireClassAddedEvent;
                _classes.ItemRemoved -= FireClassRemovedEvent;
            }

            internal void AddDMClass(DMClass dmClass)
            {
                ClassModel classModel = new ClassModel(dmClass.ClassNameBO);
                classModel.DMClass = dmClass;
                this.Classes.Add(classModel);
                dmClass.PropertyUpdated += DMClassPropertyUpdatedHandler;
            }

            private void DMClassPropertyUpdatedHandler(object sender, BOPropUpdatedEventArgs e)
            {
                DMClass dmClass = (DMClass) e.BusinessObject;
                GetModelForClass(dmClass).Name = ((DMClass) e.BusinessObject).ClassNameBO;
            }


            internal void RemoveDMClass(DMClass dmClass)
            {
                ClassModel classModelToRemove = GetModelForClass(dmClass);
                if (!this.Classes.Contains(classModelToRemove)) return;
                this.Classes.Remove(classModelToRemove);
                dmClass.PropertyUpdated -= DMClassPropertyUpdatedHandler;
            }


            private ClassModel GetModelForClass(DMClass dmClass)
            {
                foreach (ClassModel classModel in _classes)
                {
                    if (classModel.DMClass == dmClass) return classModel;
                }
                return null;
            }
        }

        public class ClassModel : INotifyPropertyChanged
        {
            private string _name;

            public ClassModel(string name)
            {
                Name = name;
            }

            public string Name
            {
                get { return _name; }
                set
                {
                    _name = value;
                    FirePropertyChangedEvent("Name");
                }
            }

            private void FirePropertyChangedEvent(string propertyName)
            {
                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }

            internal DMClass DMClass { get; set; }
            public event PropertyChangedEventHandler PropertyChanged;
        }


        public static SolutionViewModel BuildFrom(DMSolution solution)
        {
            SolutionViewModel model = new SolutionViewModel(solution.SolutionName) {DMSolution = solution};
            foreach (DMAssembly assembly in solution.Assemblies)
            {
                model.AddAssemblyToModel(assembly);
            }

            solution.Assemblies.BusinessObjectAdded +=
                (sender, e) => model.AddAssemblyToModel((DMAssembly) e.BusinessObject);
            solution.Assemblies.BusinessObjectRemoved +=
                (sender, e) => model.RemoveAssemblyFromModel((DMAssembly) e.BusinessObject);
            return model;
        }
    }
}